/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.catalina.session;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.IOException;

import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Manager;
import org.apache.catalina.Store;
import org.apache.catalina.util.LifecycleSupport;
import org.apache.catalina.util.StringManager;

/**
 * Abstract implementation of the Store interface to support most of the
 * functionality required by a Store.
 * 
 * @author Bip Thelin
 * @version $Revision: 467222 $, $Date: 2006-10-24 11:17:11 +0800 (星期二, 24 十月
 *          2006) $
 */

public abstract class StoreBase implements Lifecycle, Store {

	// ----------------------------------------------------- Instance Variables

	/**
	 * The descriptive information about this implementation.
	 */
	protected static String info = "StoreBase/1.0";

	/**
	 * Name to register for this Store, used for logging.
	 */
	protected static String storeName = "StoreBase";

	/**
	 * Has this component been started yet?
	 */
	protected boolean started = false;

	/**
	 * The lifecycle event support for this component.
	 */
	protected LifecycleSupport lifecycle = new LifecycleSupport(this);

	/**
	 * The property change support for this component.
	 */
	protected PropertyChangeSupport support = new PropertyChangeSupport(this);

	/**
	 * The string manager for this package.
	 */
	protected StringManager sm = StringManager.getManager(Constants.Package);

	/**
	 * The Manager with which this JDBCStore is associated.
	 */
	protected Manager manager;

	// ------------------------------------------------------------- Properties

	/**
	 * Return the info for this Store.
	 */
	public String getInfo() {
		return (info);
	}

	/**
	 * Return the name for this Store, used for logging.
	 */
	public String getStoreName() {
		return (storeName);
	}

	/**
	 * Set the Manager with which this Store is associated.
	 * 
	 * @param manager
	 *            The newly associated Manager
	 */
	public void setManager(Manager manager) {
		Manager oldManager = this.manager;
		this.manager = manager;
		support.firePropertyChange("manager", oldManager, this.manager);
	}

	/**
	 * Return the Manager with which the Store is associated.
	 */
	public Manager getManager() {
		return (this.manager);
	}

	// --------------------------------------------------------- Public Methods

	/**
	 * Add a lifecycle event listener to this component.
	 * 
	 * @param listener
	 *            The listener to add
	 */
	public void addLifecycleListener(LifecycleListener listener) {
		lifecycle.addLifecycleListener(listener);
	}

	/**
	 * Get the lifecycle listeners associated with this lifecycle. If this
	 * Lifecycle has no listeners registered, a zero-length array is returned.
	 */
	public LifecycleListener[] findLifecycleListeners() {

		return lifecycle.findLifecycleListeners();

	}

	/**
	 * Remove a lifecycle event listener from this component.
	 * 
	 * @param listener
	 *            The listener to add
	 */
	public void removeLifecycleListener(LifecycleListener listener) {
		lifecycle.removeLifecycleListener(listener);
	}

	/**
	 * Add a property change listener to this component.
	 * 
	 * @param listener
	 *            a value of type 'PropertyChangeListener'
	 */
	public void addPropertyChangeListener(PropertyChangeListener listener) {
		support.addPropertyChangeListener(listener);
	}

	/**
	 * Remove a property change listener from this component.
	 * 
	 * @param listener
	 *            The listener to remove
	 */
	public void removePropertyChangeListener(PropertyChangeListener listener) {
		support.removePropertyChangeListener(listener);
	}

	// --------------------------------------------------------- Protected
	// Methods

	/**
	 * Called by our background reaper thread to check if Sessions saved in our
	 * store are subject of being expired. If so expire the Session and remove
	 * it from the Store.
	 * 
	 */
	public void processExpires() {
		long timeNow = System.currentTimeMillis();
		String[] keys = null;

		if (!started) {
			return;
		}

		try {
			keys = keys();
		} catch (IOException e) {
			manager.getContainer().getLogger().error("Error getting keys", e);
			return;
		}
		if (manager.getContainer().getLogger().isDebugEnabled()) {
			manager.getContainer().getLogger().debug(
					getStoreName() + ": processExpires check number of "
							+ keys.length + " sessions");
		}

		for (int i = 0; i < keys.length; i++) {
			try {
				StandardSession session = (StandardSession) load(keys[i]);
				if (session == null) {
					continue;
				}
				if (session.isValid()) {
					continue;
				}
				if (manager.getContainer().getLogger().isDebugEnabled()) {
					manager.getContainer().getLogger().debug(
							getStoreName()
									+ ": processExpires expire store session "
									+ keys[i]);
				}
				if (((PersistentManagerBase) manager).isLoaded(keys[i])) {
					// recycle old backup session
					session.recycle();
				} else {
					// expire swapped out session
					session.expire();
				}
				remove(session.getIdInternal());
			} catch (Exception e) {
				manager.getContainer().getLogger().error(
						"Session: " + keys[i] + "; ", e);
				try {
					remove(keys[i]);
				} catch (IOException e2) {
					manager.getContainer().getLogger().error(
							"Error removing key", e2);
				}
			}
		}
	}

	// --------------------------------------------------------- Thread Methods

	/**
	 * Prepare for the beginning of active use of the public methods of this
	 * component. This method should be called after <code>configure()</code>,
	 * and before any of the public methods of the component are utilized.
	 * 
	 * @exception LifecycleException
	 *                if this component detects a fatal error that prevents this
	 *                component from being used
	 */
	public void start() throws LifecycleException {
		// Validate and update our current component state
		if (started)
			throw new LifecycleException(sm.getString(getStoreName()
					+ ".alreadyStarted"));
		lifecycle.fireLifecycleEvent(START_EVENT, null);
		started = true;

	}

	/**
	 * Gracefully terminate the active use of the public methods of this
	 * component. This method should be the last one called on a given instance
	 * of this component.
	 * 
	 * @exception LifecycleException
	 *                if this component detects a fatal error that needs to be
	 *                reported
	 */
	public void stop() throws LifecycleException {
		// Validate and update our current component state
		if (!started)
			throw new LifecycleException(sm.getString(getStoreName()
					+ ".notStarted"));
		lifecycle.fireLifecycleEvent(STOP_EVENT, null);
		started = false;

	}

}
